﻿
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using FibMatrix;
using FM_Mono;
using Debug = UnityEngine.Debug;
using UnityEngine.Networking;
using FibMatrix.IO;

public class ResourceHelper
{

//	private static int m_id = 0;
//	private static int m_totalSize = 0;
//
//	private static Dictionary<int, OneFileInfor> m_allFileInfoDic = new Dictionary<int, OneFileInfor>();
//
//
//	/** 遍历文件夹获取所有文件信息 **/
//	private static void TraverseFolder(string folderpath, Dictionary<string, AssetNode> infoMap)
//	{
//		/** 读取文件夹下面所有文件的信息 **/
//		DirectoryInfo dirInfo = new DirectoryInfo(folderpath);
//
//		foreach (FileInfo fileinfo in dirInfo.GetFiles("*.bytes", SearchOption.AllDirectories))
//		{
//			string filename = fileinfo.FullName.Replace("\\", "/");
//			filename = filename.Remove(0, filename.LastIndexOf("/Assets"));
//			string key = filename.Remove(0, 1);
//			AssetNode node = infoMap[key];
//
//			int filesize = (int)fileinfo.Length;
//
////			Debug.Log(m_id + " : " + filename + " 文件大小: " + filesize);
//
//			OneFileInfor info = new OneFileInfor();
//			info.m_id = m_id;
//			info.m_Size = filesize;
//			info.compressed = node.compressed;
//			info.m_Path = filename;
//			info.m_PathLength = new UTF8Encoding().GetBytes(filename).Length;
//
//			/**  读取这个文件  **/
//			FileStream fileStreamRead = new FileStream(fileinfo.FullName, FileMode.Open, FileAccess.Read);
//			byte[] filedata = new byte[filesize];
//			fileStreamRead.Read(filedata, 0, filesize);
//			info.m_data = filedata;
//			fileStreamRead.Close();
//
//
//			m_allFileInfoDic.Add(m_id, info);
//
//			m_id++;
//			m_totalSize += filesize;
//		}
//	}
//
//
//	/**  打包一个文件夹  **/
//	public static IEnumerator PackFolder(string folderpath, string upkfilepath, CodeProgress progress, Action packCompleteCallback, Dictionary<string, AssetNode> infoMap)
//	{
//		/** 重置数据 **/
//		m_id = 0;
//		m_totalSize = 0;
//		m_allFileInfoDic.Clear();
//
//		TraverseFolder(folderpath, infoMap);
//
//		/**  更新文件在UPK中的起始点  **/
//		int firstfilestartpos = 0 + 4;
//		for (int index = 0; index < m_allFileInfoDic.Count; index++)
//		{
//			firstfilestartpos += 4 + 4 + 4 + 4 + 1 + m_allFileInfoDic[index].m_PathLength;
//		}
//
//		int startpos = 0;
//		for (int index = 0; index < m_allFileInfoDic.Count; index++)
//		{
//			if (index == 0)
//			{
//				startpos = firstfilestartpos;
//			}
//			else
//			{
//				startpos = m_allFileInfoDic[index - 1].m_StartPos + m_allFileInfoDic[index - 1].m_Size;//上一个文件的开始+文件大小;
//			}
//
//			m_allFileInfoDic[index].m_StartPos = startpos;
//		}
//
//		/**  写文件  **/
//		FileStream fileStream = new FileStream(upkfilepath, FileMode.Create);
//
//		/**  文件总数量  **/
//		byte[] totaliddata = System.BitConverter.GetBytes(m_id);
//		fileStream.Write(totaliddata, 0, totaliddata.Length);
//
//		for (int index = 0; index < m_allFileInfoDic.Count; index++)
//		{
//			/** 写入ID **/
//			byte[] iddata = System.BitConverter.GetBytes(m_allFileInfoDic[index].m_id);
//			fileStream.Write(iddata, 0, iddata.Length);
//
//			/**  写入StartPos  **/
//			byte[] startposdata = System.BitConverter.GetBytes(m_allFileInfoDic[index].m_StartPos);
//			fileStream.Write(startposdata, 0, startposdata.Length);
//
//			/**  写入size  **/
//			byte[] sizedata = System.BitConverter.GetBytes(m_allFileInfoDic[index].m_Size);
//			fileStream.Write(sizedata, 0, sizedata.Length);
//
//			/**  写入pathLength  **/
//			byte[] pathLengthdata = System.BitConverter.GetBytes(m_allFileInfoDic[index].m_PathLength);
//			fileStream.Write(pathLengthdata, 0, pathLengthdata.Length);
//
//			byte[] compressdata = System.BitConverter.GetBytes(m_allFileInfoDic[index].compressed);
//			fileStream.Write(compressdata, 0, compressdata.Length);
//
//			/**  写入path  **/
//			byte[] mypathdata = new UTF8Encoding().GetBytes(m_allFileInfoDic[index].m_Path);
//
//			fileStream.Write(mypathdata, 0, mypathdata.Length);
//		}
//
//		int totalprocessSize = 0;
//		foreach (var infopair in m_allFileInfoDic)
//		{
//			OneFileInfor info = infopair.Value;
//			int size = info.m_Size;
//			byte[] tmpdata = null;
//			int processSize = 0;
//			while (processSize < size)
//			{
//				if (size - processSize < 1024)
//				{
//					tmpdata = new byte[size - processSize];
//				}
//				else
//				{
//					tmpdata = new byte[1024];
//				}
//				fileStream.Write(info.m_data, processSize, tmpdata.Length);
//
//				processSize += tmpdata.Length;
//				totalprocessSize += tmpdata.Length;
//
//				progress.SetProgressPercent(m_totalSize, totalprocessSize);
//				yield return 0;
//			}
//		}
//
//		fileStream.Flush();
//		fileStream.Close();
//
//
//		/** 重置数据 **/
//		m_id = 0;
//		m_totalSize = 0;
//		m_allFileInfoDic.Clear();
//
//		packCompleteCallback();
//
//	}
//
	// public interface ILongRunTask
	// {
	// 	bool isCompleted
	// 	{
	// 		get;
	// 	}

	// 	float progress
	// 	{
	// 		get;
	// 	}

	// 	void Dispose();
	// }
//
//	public class ExtractContext:ILongRunTask
//	{
//		public Thread task;
//		private volatile float _progress;
//		public float progress
//		{
//			get { return _progress; }
//
//			set { _progress = value; }
//		}
//		public List<OneFileInfor> fileList;
//		public byte[] dataBuffer;
//
//		public ExtractContext(List<OneFileInfor> extractList, byte[] buffer, Thread thread)
//		{
//			task = thread;
//			fileList = extractList;
//			dataBuffer = buffer;
//			_progress = 0;
//		}
//
//		public bool isCompleted
//		{
//			get { return task.ThreadState == System.Threading.ThreadState.Stopped; }
//		}
//
//	}
//
//	private static System.Text.UTF8Encoding m_UTF8Encoding = new System.Text.UTF8Encoding();
//
//	public static ExtractContext[] ExtraUPK(byte[] dataBuffer, string outputpath, HashSet<string> filterPathSet, int splitCount)
//	{
//		using (Stream upkSourceStream = new MemoryStream(dataBuffer, false))
//		{
//
//			int totalsize = 0;
//
//			upkSourceStream.Seek(0, SeekOrigin.Begin);
//
//			int offset = 0;
//
//			//读取文件数量;
//			byte[] totaliddata = new byte[4];
//			upkSourceStream.Read(totaliddata, 0, 4);
//			int filecount = BitConverter.ToInt32(totaliddata, 0);
//			offset += 4;
//
//			//读取所有文件信息;
//			for (int index = 0; index < filecount; index++)
//			{
//				//读取id;
//				byte[] iddata = new byte[4];
//				upkSourceStream.Seek(offset, SeekOrigin.Begin);
//				upkSourceStream.Read(iddata, 0, 4);
//				int id = BitConverter.ToInt32(iddata, 0);
//				offset += 4;
//
//				//读取StartPos;
//				byte[] startposdata = new byte[4];
//				upkSourceStream.Seek(offset, SeekOrigin.Begin);
//				upkSourceStream.Read(startposdata, 0, 4);
//				int startpos = BitConverter.ToInt32(startposdata, 0);
//				offset += 4;
//
//				//读取size;
//				byte[] sizedata = new byte[4];
//				upkSourceStream.Seek(offset, SeekOrigin.Begin);
//				upkSourceStream.Read(sizedata, 0, 4);
//				int size = BitConverter.ToInt32(sizedata, 0);
//				offset += 4;
//
//				//读取pathLength;
//				byte[] pathLengthdata = new byte[4];
//				upkSourceStream.Seek(offset, SeekOrigin.Begin);
//				upkSourceStream.Read(pathLengthdata, 0, 4);
//				int pathLength = BitConverter.ToInt32(pathLengthdata, 0);
//				offset += 4;
//
//				byte[] compressdata = new byte[1];
//				upkSourceStream.Seek(offset, SeekOrigin.Begin);
//				upkSourceStream.Read(compressdata, 0, 1);
//				bool compressed = BitConverter.ToBoolean(compressdata, 0);
//				offset += 1;
//
//				//读取path;
//				byte[] pathdata = new byte[pathLength];
//				upkSourceStream.Seek(offset, SeekOrigin.Begin);
//				upkSourceStream.Read(pathdata, 0, pathLength);
//				string path = m_UTF8Encoding.GetString(pathdata);
//				offset += pathLength;
//
//
//				//添加到Dic;
//				OneFileInfor info = new OneFileInfor();
//				info.m_id = id;
//				info.m_Size = size;
//				info.m_PathLength = pathLength;
//				info.m_Path = path;
//				info.compressed = compressed;
//				info.m_StartPos = startpos;
//
//				m_allFileInfoDic.Add(id, info);
//
//				totalsize += size;
//			}
//		}
//
//		List<OneFileInfor>[] extractFilesSet = new List<OneFileInfor>[splitCount];
//
//		for (int i = 0; i < splitCount; ++i)
//		{
//			extractFilesSet[i] = new List<OneFileInfor>();
//		}
//
//		List<OneFileInfor> totalFiles = new List<OneFileInfor>();
//
//		foreach (var fileinfo in m_allFileInfoDic.Values)
//		{
//			if (filterPathSet == null || filterPathSet.Contains(fileinfo.m_Path))
//			{
//				totalFiles.Add(fileinfo);
//
//				string filepath = outputpath + fileinfo.m_Path;
//				string dirpath = Path.GetDirectoryName(filepath);
//				if (dirpath != null && !Directory.Exists(dirpath))
//				{
//					Directory.CreateDirectory(dirpath);
//				}
//				fileinfo.m_Path = filepath;
//			}
//		}
//		int splitLen = totalFiles.Count / splitCount;
//
//		for (int i = 0; i < totalFiles.Count; ++i)
//		{
//			int index = i / splitLen;
//			if (index >= extractFilesSet.Length)
//			{
//				index = extractFilesSet.Length - 1;
//			}
//			extractFilesSet[index].Add(totalFiles[i]);
//		}
//
//		ExtractContext[] contexts = new ExtractContext[splitCount];
//		for (int i = 0; i < splitCount; ++i)
//		{
//			contexts[i] = new ExtractContext(extractFilesSet[i], dataBuffer, new Thread(ExractCompressedFiles));
//			contexts[i].task.Start(contexts[i]);
//			
//		}
//		return contexts;
////		//释放文件;
////		int totalprocesssize = 0;
////		foreach (var infopair in m_allFileInfoDic)
////		{
////			OneFileInfor info = infopair.Value;
////
////			int startPos = info.m_StartPos;
////			int size = info.m_Size;
////			string path = info.m_Path;
////
////			//创建文件;
////			string dirpath = outputpath + path.Substring(0, path.LastIndexOf('/'));
////			string filepath = outputpath + path;
////			if (Directory.Exists(dirpath) == false)
////			{
////				Directory.CreateDirectory(dirpath);
////			}
////			if (File.Exists(filepath))
////			{
////				File.Delete(filepath);
////			}
////
////			FileStream fileStream = new FileStream(filepath, FileMode.Create);
////
////			byte[] tmpfiledata;
////			int processSize = 0;
////			while (processSize < size)
////			{
////				if (size - processSize < 1024)
////				{
////					tmpfiledata = new byte[size - processSize];
////				}
////				else
////				{
////					tmpfiledata = new byte[1024];
////				}
////
////				//读取;
////				upkSourceStream.Seek(startPos + processSize, SeekOrigin.Begin);
////				upkSourceStream.Read(tmpfiledata, 0, tmpfiledata.Length);
////
////				//写入;
////				fileStream.Write(tmpfiledata, 0, tmpfiledata.Length);
////
////				processSize += tmpfiledata.Length;
////				totalprocesssize += tmpfiledata.Length;
////
////				progress.SetProgressPercent((long)totalsize, (long)totalprocesssize);
////			}
////			fileStream.Flush();
////			fileStream.Close();
////		}
//	}
//
//	static void ExractCompressedFiles(object state)
//	{
//		ExtractContext context = state as ExtractContext;
//		List<OneFileInfor> fileList = context.fileList;
//		byte[] dataBuffer = context.dataBuffer;
//		int totalCount = fileList.Count;
//		for(int i = 0; i < totalCount; ++i)
//		{
//			OneFileInfor fileInfo = fileList[i];
//			context.progress = (float) (i + 1) / totalCount;
//			if (fileInfo.compressed)
//			{
//				using (MemoryStream ms = new MemoryStream(dataBuffer, fileInfo.m_StartPos, fileInfo.m_Size, false))
//				using (FileStream fs = new FileStream(fileInfo.m_Path, FileMode.OpenOrCreate))
//				{
//					SevenZipHelper.DecompressFile(ms, fs);
//				}
//			}
//			else
//			{
//				using (FileStream fs = new FileStream(fileInfo.m_Path, FileMode.OpenOrCreate))
//				{
//					fs.Write(dataBuffer, fileInfo.m_StartPos, fileInfo.m_Size);
//					fs.Flush();
//				}
//			}
//		}
//		
//	}

	public static unsafe void Copy(byte[] source, int sourceOffset, byte[] target,
		int targetOffset, int count)
	{
		// If either array is not instantiated, you cannot complete the copy.
		if ((source == null) || (target == null))
		{
			throw new System.ArgumentException();
		}

		// If either offset, or the number of bytes to copy, is negative, you
		// cannot complete the copy.
		if ((sourceOffset < 0) || (targetOffset < 0) || (count < 0))
		{
			throw new System.ArgumentException();
		}

		// If the number of bytes from the offset to the end of the array is 
		// less than the number of bytes you want to copy, you cannot complete
		// the copy. 
		if ((source.Length - sourceOffset < count) ||
			(target.Length - targetOffset < count))
		{
			throw new System.ArgumentException();
		}

		// The following fixed statement pins the location of the source and
		// target objects in memory so that they will not be moved by garbage
		// collection.
		fixed (byte* pSource = source, pTarget = target)
		{
			// Set the starting points in source and target for the copying.
			byte* ps = pSource + sourceOffset;
			byte* pt = pTarget + targetOffset;

			// Copy the specified number of bytes from source to target.
			for (int i = 0; i < count; i++)
			{
				*pt = *ps;
				pt++;
				ps++;
			}
		}
	}

	private static StringBuilder _cachedBuilder = new StringBuilder(100);
	/// <summary>
	/// 拼接文件路径，形如{prefix}/{platform}/{relativeURLWithoutExtention}{_version}{relativeURLExtension}
	/// 其中platform, version可选，可以拼出cdnUrl，本地存储路径（带md5，不带md5，编辑器构建用的，带前缀运行时保存用的）
	/// </summary>
	/// <param name="prefix"></param>
	/// <param name="relativeURL"></param>
	/// <param name="version">md5当作版本</param>
	/// <param name="usePlatform">添加平台相关的，prefix后，文件前的中缀</param>
	/// <returns></returns>
    public static string GetAssetURL(string prefix, string relativeURL, string version, bool usePlatform = true)
	{
		_cachedBuilder.Length = 0;
		StringBuilder url = _cachedBuilder;

		if (string.IsNullOrEmpty(prefix) == false)
		{
			url.Append(prefix);
			if (usePlatform)
			{
				if (ResourceDevCfg.Instance.IsDebugStreamPlay())
				{
#if UNITY_WEBGL && !UNITY_EDITOR
					url.Append($"/{ResourcePathConst.WX_CACHEABLE_URL_TAG}/");
#else
					url.Append("/");
#endif
				}
				else
				{
#if UNITY_IOS
					string platformName = "/APP/assets-ios/";
#elif UNITY_ANDROID
					string platformName = "/APP/assets-android/";
#elif UNITY_WEBGL
					string platformName = $"{ResourcePathConst.WX_CDN_PLATFORM_PATH}/{ResourcePathConst.WX_CACHEABLE_URL_TAG}/";
#else
					string platformName = "/";
#endif
					url.Append(platformName);
				}
			}
			else
			{
				url.Append("/");
			}
		}

		// 后缀前加入version
		if (!string.IsNullOrEmpty(version))
		{
			int extensionIndex = relativeURL.LastIndexOf('.');
			if (extensionIndex != -1)
			{
				if (extensionIndex > 0)
				{
					url.Append(relativeURL, 0, extensionIndex);
				}
				url.Append('_');
				url.Append(version);
				url.Append(relativeURL, extensionIndex, relativeURL.Length - extensionIndex);
			}
			else
			{
				url.Append('_');
				url.Append(version);
			}
		}
		else
		{
			url.Append(relativeURL);
		}

		return url.ToString();
	}

	/// <summary>
	/// 获取文件实际的存储路径，相对于REMOTE_ASSET_DIRECTORY
	/// webgl下都是有内容md5的，小游戏要基于md5删除旧文件
	/// 其他平台保存路径没md5，会覆盖同名文件
	/// </summary>
	/// <param name="originalUrl"></param>
	/// <param name="md5"></param>
	/// <returns></returns>
	public static string GetRuntimeSavePath(string originalUrl, string md5)
	{
#if UNITY_WEBGL
		return GetAssetURL(ResourcePathConst.WX_CACHEABLE_URL_TAG, originalUrl, md5, false);
#else
	    return originalUrl;
#endif

	}


    private static Queue<BundleInfo> _bfsQueue = new Queue<BundleInfo>();
    private static HashSet<BundleInfo> _loadSet = new HashSet<BundleInfo>();
    ///////////////////
	/// 像是把直接依赖转为全部依赖，虽然使用宽度优先遍历，但结果不必然满足拓扑排序（后面的不依赖前面的）
	/// 比如A->(B, C), C->B, 返回ABC，受构建时数据影响
	/////////////////// 
    [IgnoreGen]
    internal static List<BundleInfo> ProcessLoadDependence(BundleInfo mainHint, 
		List<BundleInfo> result = null)
    {
        if (_bfsQueue.Count > 0)
        {
            _bfsQueue.Clear();
        }

        if (result == null)
        {
            result = new List<BundleInfo>();
        }
        else if (result.Count > 0)
        {
            result.Clear();
        }

        if (_loadSet.Count > 0)
        {
            _loadSet.Clear();
        }

        BundleInfo hint;
        _bfsQueue.Enqueue(mainHint);
        while (_bfsQueue.Count > 0)
        {
            hint = _bfsQueue.Dequeue();
            // ++hint.bundleRefCount;	// 不应该加
            /*if (hint.id.ToLower().Contains("icon01atlas"))
			{
				Debug.Log(hint.id + " " + hint.bundleRefCount + " mainHint " + mainHint.id);
			}*/
            if (!_loadSet.Contains(hint))
            {
                result.Add(hint);
                _loadSet.Add(hint);
            }

            List<BundleInfo> dependence = hint.dependence;
            int childCount = dependence != null ? dependence.Count : 0;
            for (int i = 0; i < childCount; ++i)
            {
                _bfsQueue.Enqueue(dependence[i]);
            }
        }
        return result;
    }

    private static List<BundleInfo> _cachedIndependentList = new List<BundleInfo>(1){null};

    [IgnoreGen]
    internal static List<BundleInfo> GetFixedFlatDependences(BundleInfo hint, bool useCachedWhenIndependent = true)
    {
        if (hint.flatDependence != null)
        {
            return hint.flatDependence;
        }
        if (useCachedWhenIndependent)
        {
            _cachedIndependentList[0] = hint;
            return _cachedIndependentList;
        }
        return new List<BundleInfo> { hint };
    }

    /// <summary>
    /// 通过资源列表获取assetcfg中依赖的所有文件，
    /// </summary>
    /// <param name="resList"></param>
    /// <param name="assets">路径是assetNode的id，跟assetDB文件后会可能不同</param>
    /// <param name="fileWithMd5">true返回带md5的部署文件路径，false返回资源id</param>
    /// <returns></returns>
    [IgnoreGen]
    public static List<string> GetAllDependenciesUrlByAssets(AssetList resList, string[] assets, bool fileWithMd5)
    {
	    List<AssetNode> set = GetAllDependenciesNodeByAssets(resList, assets);

	    List<string> res = new List<string>();
	    foreach (var v in set)
	    {
		    if (fileWithMd5)
			    res.Add(GetAssetURL("", v.url, v.md5, false));
		    else
			    res.Add(v.id);
	    }
	    
	    // StringBuilder sb = new StringBuilder();
	    // foreach (var v in res)
		   //  sb.AppendLine(v);
	    // Debug.LogError(sb);

	    return res;
    }
    
    /// <summary>
    /// 通过资源列表获取assetcfg中依赖的所有节点，
    /// </summary>
    /// <param name="resList"></param>
    /// <param name="assets"></param>
    /// <returns></returns>
    [IgnoreGen]
    public static List<AssetNode> GetAllDependenciesNodeByAssets(AssetList resList, string[] assets)
    {
	    HashSet<AssetNode> set = new HashSet<AssetNode>();
	    foreach (var v in assets)
	    {
		    if (string.IsNullOrEmpty(v))
			    continue;
		    if (resList.TryGetValue(v, out var node))
		    {
			    AddDependency(resList, node, set);
		    }
		    else
		    {
			    Debug.LogError($"assetConfig没找到资源对应文件，可能要用修改的后缀: {v}");
		    }
	    }

	    return set.ToList();
    }
    private static void AddDependency(AssetList resList, AssetNode node, HashSet<AssetNode> set)
    {
	    if (set.Contains(node))
		    return;
	    set.Add(node);
	    if (node.dependIndexes != null)
	    {
		    foreach (var v in node.dependIndexes)
		    {
			    AddDependency(resList, resList[v], set);
		    }
	    }
    }

    public static void SaveWebRequestData(UnityWebRequest req, string path)
    {
	    if (req == null || string.IsNullOrEmpty(path)) return;
	    var data = req.downloadHandler.data;
	    if (data == null || data.Length == 0)
		    Debug.LogError($"webReq has no data, {req.url}");
	    StorageManager.CreateDirectoryIfNotExist(path);
	    StorageManager.WriteAllBytes(path, data);
    }

    public static string GetDebugCdn()
    {
	    return ResourceDevCfg.Instance.GetDebugCdn();
    }

    public static bool IsDebugStreamPlay => ResourceDevCfg.Instance.IsDebugStreamPlay();

}
